import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.TreeSet;


/*Copyright  Feliks Rozenberg

                  Licensed under the Apache License, Version 2.0 (the "License");
                  you may not use this file except in compliance with the License.
                  You may obtain a copy of the License at

                      http://www.apache.org/licenses/LICENSE-2.0

                  Unless required by applicable law or agreed to in writing, software
                  distributed under the License is distributed on an "AS IS" BASIS,
                  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                  See the License for the specific language governing permissions and
                  limitations under the License.*/



public class LanguageGuesserProgram
{
  
  private static double countUnigramsInAllfiles = 0.0;
  public static void main(String[] args) 
  {
    //reading the files containing the probabilities into corresponding hashmaps
    String[] fileNames = {"prbsslovak.txt", "prbsbulgarian.txt", "prbsczech.txt", "prbsdanish.txt", 
      "prbsenglish.txt", "prbsestonian.txt",  "prbsfrench.txt", "prbsdutch.txt", "prbsfinnish.txt", "prbsgerman.txt", "prbsswedish.txt",
      "prbsgreek.txt", "prbshungarian.txt", "prbsitalian.txt", "prbslatvian.txt",
      "prbslithuanian.txt", "prbspolish.txt", "prbsportuguese.txt", "prbsromanian.txt", "prbsslovenian.txt",
      "prbsspanish.txt"};   
    
    
    
    Set<String> setOfUnigrams = new TreeSet<String>();
    ArrayList<Double> fileKToNumberOfUnigrams = new ArrayList<Double>();
    ArrayList<Double> fileKToSumOfProbabilitiesOfUnigrams = new ArrayList<Double>();
    
    //double probsSum = 0.0;
    Scanner input = null;
    System.out.println("The program is preparing the language models. This might take a while...");
    //int m=0;
    //array list to store the hashMaps corresponding to every language model (chars to probabilities)
    List<Map<String, Double>> listOfMaps = new ArrayList<Map<String, Double>>();
    //read every probabilities file, extract the probabilities into a hashMap corresponding to each language
    for(String fileName: fileNames)
    {
      double probsSum = 0.0;
      double countUnigrams = 0.0; 
      try
      {
        input = new Scanner (new File(fileName));
      }
      catch(FileNotFoundException e)
      {
        System.out.println("Could not open the file " + fileName);
        System.exit(0);
      }
      input.useDelimiter("end of probabilities");
      //storing the text as a string and then after removing whitespaces in an array
      System.out.println(fileName);
      String text = input.next();
      
      //System.out.println("text length: " + text.length());
      String[] prbs;
      prbs = text.split("\\n+");
      //System.out.println("The language: " + fileName + " and the array length: " +prbs.length);
      //System.out.println("array length: " + prbs.length);
      //array list to store the hashMaps corresponding to every language model (chars to probabilities)
      HashMap<String, Double> hashMapPrbs = new HashMap<String, Double>();
      for(int i=0; i<prbs.length-1; i+=2)
      {
        String key = prbs[i];
        if (key.length()==1)
        {
          setOfUnigrams.add(key);
          countUnigrams++;
          probsSum = probsSum + Double.parseDouble(prbs[i+1]);
        }
        double value = Double.parseDouble(prbs[i+1]);
        //System.out.println("keylength: " + key + " value" +value);
        hashMapPrbs.put(key, value);
      }
//      System.out.println(hashMapPrbs.toString());
//      System.out.println("Stop!!!!!!!!!!!!!!!!!!!");
      fileKToNumberOfUnigrams.add(countUnigrams);
      
      fileKToSumOfProbabilitiesOfUnigrams.add(probsSum);
      listOfMaps.add(hashMapPrbs);
    }
    
    countUnigramsInAllfiles = setOfUnigrams.size();
    outer: while(true)
    { 
      System.out.println("Please input a sentence and the program will detect the language the sentence is written in:\n to quit type 'exit' ");
      Scanner keyboard = new Scanner(System.in, "UTF-8");
      String inputString= keyboard.nextLine(); 
      if(inputString.equals("exit"))
        break outer;
      System.out.println("input string outside loop: " +inputString);
      int contextLength = 5;
      
      // System.out.println("List of Maps size: " + listOfMaps.size() + " and num of languages is: " 
      // + fileNames.length);
      //a hashmap mapping between a language and the probability for the input string
      HashMap<String, Double> languageToProbability = new HashMap<String, Double>(); 
      //a hashmap mapping between an alpha string and the respective value
      HashMap<String, Double> alphaToValue = new HashMap<String, Double>();
      //going over the arraylist containing the probabilities for different languages
      for(int k=0; k<listOfMaps.size(); k++)
      {
        int j=0; 
        //System.out.println(listOfMaps.get(k));
        double prb = 0.0;
        //System.out.println("unigram: " + inputString.substring(j, j+1));
        String temp = inputString;
        String key = temp.substring(0, 1);
        //System.out.println("input string inside loop: " +inputString);
        //System.out.println("key inside loop: " +key);
        
        if (listOfMaps.get(k).get(key) ==null)
        {
          prb = Math.log(backOffProbability(key, alphaToValue, listOfMaps.get(k), fileKToSumOfProbabilitiesOfUnigrams.get(k),
                                    fileKToNumberOfUnigrams.get(k)));
          //prb = Double.NEGATIVE_INFINITY;
        }
        else
        {
          prb = Math.log(listOfMaps.get(k).get(key));
          //System.out.println("unigram: " + key  + " the language: " + fileNames[k]);
        }
        
        for (int i=2; i<inputString.length()+1; i++)
        {
          //length of context to look at
          if(i>contextLength)
            j++;
          //System.out.println("ngram: " + inputString.substring(j, i));
          String temp2 = inputString;
          String key2 = temp2.substring(j, i);
          
          if (listOfMaps.get(k).get(key2) ==null)
          {
            prb = prb +  Math.log(backOffProbability(key2, alphaToValue, listOfMaps.get(k), fileKToSumOfProbabilitiesOfUnigrams.get(k), 
                                             fileKToNumberOfUnigrams.get(k)));
            //prb = Double.NEGATIVE_INFINITY;
          }
          else
          {
            prb = prb +  Math.log(listOfMaps.get(k).get(key2))   ; 
            //System.out.println("ngram: "+key2 + " the language: " + fileNames[k]);
          }
        }
        
        languageToProbability.put(fileNames[k], prb); 
      }
      
      //System.out.println("number of unigrams in all files together = " + countUnigramsInAllfiles);
      //sorting the languages according to the probabilities
      System.out.println("The 10 most probable languages for the input string are: ");
      LinkedHashMap<String, Double> orderedLanguages = new LinkedHashMap<String, Double>();
      orderedLanguages = sortHashMapByValues(languageToProbability, false);
      Set ref = orderedLanguages.keySet();
      Iterator it = ref.iterator();
      int countTop10=0;
      while (it.hasNext()) 
      {
        if (countTop10==10)
          break;
        String language = (String)it.next();
        double prb = orderedLanguages.get(language);
        language = language.replace("prbs", "");
        language = language.replace(".txt", "");
        System.out.println("The language: " + language +  " and the probability is: " + prb);
        countTop10++;
      } 
      
    }
    
  }
  
  //a method to return an alpha for backoff smoothing
  public static double alpha(HashMap<String,Double> alphasMap, String alphaString, Map<String,Double> mapFromListOfMaps, 
                             Double sumOfProbsOfUnigrams, Double numberOfUnigramsInFile)
  {
    double probASum = 0.0;
    double probBSum = 0.0;
    double alpha = 0.0;
    if(alphasMap.containsKey(alphaString))
      return alphasMap.get(alphaString);
    else
    {
      if(alphaString.equals(""))
      {
       alpha = (1.0 - sumOfProbsOfUnigrams) / (1.0 - (1.0/ countUnigramsInAllfiles) );  
       //System.out.println("alpha: " + alpha);
      }
      else
      {
        Set set = mapFromListOfMaps.keySet();
        Iterator iter = set.iterator();
        while (iter.hasNext())
        {
          String toCheck = (String) iter.next();
          if ((toCheck.startsWith(alphaString))
                && (toCheck.length() == alphaString.length() + 1)) 
          {
            probASum +=  mapFromListOfMaps.get(toCheck); 
            probBSum +=  mapFromListOfMaps.get(toCheck.substring(1)); 
          }
        }
        alpha = (1.0- probASum) / (1.0 - probBSum);
        //System.out.println("probASum: " + probASum + " probBSum: " + probBSum);
        alphasMap.put(alphaString, alpha);
        
      }
      //System.out.println("Alpha: " + alpha);
      return alpha; 
    }      
  }
  
  //a method to return the probability after backoff smoothing
  public static double backOffProbability(String str, HashMap<String,Double> alphasMap, Map<String,Double> mapFromListOfMaps, 
                                          Double sumOfProbsOfUnigrams, Double numberOfUnigrams)
  {
    if (mapFromListOfMaps.containsKey(str))
      return mapFromListOfMaps.get(str);
    
    else
    {
      //a special case: index out of bounds because chars are not found in file (say cyrillic in english only file)  
      if(str.length() == 1)
      {
      return (alpha(alphasMap, "", mapFromListOfMaps, sumOfProbsOfUnigrams, numberOfUnigrams)/countUnigramsInAllfiles);
      }
      
      else
      {
        double backoff = (alpha(alphasMap, str.substring(0, str.length()-1), mapFromListOfMaps, sumOfProbsOfUnigrams, numberOfUnigrams) * 
                          backOffProbability(str.substring(1), alphasMap, mapFromListOfMaps, sumOfProbsOfUnigrams, numberOfUnigrams) );
        
        //System.out.println("backoff: " + backoff);
        return backoff;
      }
    }
    
    
    
  }
  
  //a method to sort the results by descending order
  public static LinkedHashMap<String, Double> sortHashMapByValues(HashMap passedMap, boolean ascending) 
  {
    
    List mapKeys = new ArrayList(passedMap.keySet());
    List mapValues = new ArrayList(passedMap.values());
    Collections.sort(mapValues);
    Collections.sort(mapKeys);
    
    if (!ascending)
      Collections.reverse(mapValues);
    
    LinkedHashMap someMap = new LinkedHashMap();
    Iterator valueIt = mapValues.iterator();
    while (valueIt.hasNext()) 
    {
      Object val = valueIt.next();
      Iterator keyIt = mapKeys.iterator();
      while (keyIt.hasNext()) 
      {
        Object key = keyIt.next();
        if (passedMap.get(key).toString().equals(val.toString())) 
        {
          passedMap.remove(key);
          mapKeys.remove(key);
          someMap.put(key, val);
          break;
        }
      }
    }
    return someMap;
  }  
  
}
